package vehicle;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.Timer;

public final class Stoplight {

	public Stoplight(int canvasWidth, int canvasHeight, TrafficCanvas owner) {
		_owner = owner;
		_size = new Dimension((LAMP_DIAMETER + (LAMP_SPACE * 2)),
				((LAMP_DIAMETER * 3) + (LAMP_SPACE * 4)));
		_positions = new Point[4];
		_positions[0] = new Point(
				(((int) (canvasWidth / 2)) - ((int) (_size.width / 2)) - _size.height),
				(((int) (canvasHeight / 2)) - ((int) (_size.width / 2)) - _size.height));
		_positions[1] = new Point((_positions[0].x + _size.height),
				(_positions[0].y + _size.height));
		_positions[2] = new Point((_positions[1].x + _size.width),
				(_positions[1].y + _size.width));
		_positions[3] = new Point((_positions[2].x + _size.height),
				(_positions[2].y + _size.height));

		_lights = new Color[4][3];
		InitLights();

		_nsLightDuration = 8000;
		_ewLightDuration = 10000;
		_yellowDuration = 4000;

		_fsm = new StoplightContext(this);
	}

	public void start() {
		_fsm.enterStartState();
		return;
	}

	public Color getLightsColor(int direction) {
		boolean found;
		int i;
		Color retval = Color.white;

		for (i = 0, found = false; found == false && i < 3; ++i) {
			if (_lights[direction][i].getRGB() != Color.black.getRGB()) {
				retval = _lights[direction][i];
				found = true;
			}
		}
		return (retval);
	}

	public void getLightDimensions(Point direction, Point lightLocation,
			Dimension lightSize) {
		if (direction.x == TrafficCanvas.NORTH.x
				&& direction.y == TrafficCanvas.NORTH.y) {
			lightLocation.x = _positions[2].x;
			lightLocation.y = _positions[1].y;
			lightSize.width = _size.height;
			lightSize.height = _size.width;
		}
		// If the vehicle is heading east, it will pass under the
		// south light.
		else if (direction.x == TrafficCanvas.EAST.x
				&& direction.y == TrafficCanvas.EAST.y) {
			lightLocation.x = _positions[1].x;
			lightLocation.y = _positions[2].y;
			lightSize.width = _size.width;
			lightSize.height = _size.height;
		}
		// If the vehicle is heading south, it will pass under
		// the west light.
		else if (direction.x == TrafficCanvas.SOUTH.x
				&& direction.y == TrafficCanvas.SOUTH.y) {
			lightLocation.x = _positions[0].x;
			lightLocation.y = _positions[1].y;
			lightSize.width = _size.height;
			lightSize.height = _size.width;
		}
		// If the vehicle is heading west, it will pass under the
		// north light.
		else if (direction.x == TrafficCanvas.WEST.x
				&& direction.y == TrafficCanvas.WEST.y) {
			lightLocation.x = _positions[1].x;
			lightLocation.y = _positions[0].y;
			lightSize.width = _size.width;
			lightSize.height = _size.height;
		}

		return;
	}

	public int getLightDuration(int light) {
		int duration;

		switch (light) {
		case TrafficCanvas.NS_LIGHT:
			duration = _nsLightDuration;
			break;

		case TrafficCanvas.EW_LIGHT:
			duration = _ewLightDuration;
			break;

		case TrafficCanvas.YELLOW_LIGHT:
		default:
			duration = _yellowDuration;
			break;
		}

		return (duration);
	}

	public void setLightDuration(int light, int duration) {
		switch (light) {
		case TrafficCanvas.NS_LIGHT:
			_nsLightDuration = duration;
			break;

		case TrafficCanvas.EW_LIGHT:
			_ewLightDuration = duration;
			break;

		case TrafficCanvas.YELLOW_LIGHT:
			_yellowDuration = duration;
			break;
		}

		return;
	}

	public void startDemo() {
		_fsm.Start();
		return;
	}

	public void pauseDemo() {
		_fsm.Pause();
		return;
	}

	public void continueDemo() {
		_fsm.Continue();
		return;
	}

	public void stopDemo() {
		_fsm.Stop();
		return;
	}

	public void paint(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;

		// Draw the four stop light boxes. Start with the
		// northern light and go clockwise.
		g2.setPaint(Color.black);
		g2.fill(new Rectangle2D.Double(_positions[1].x, _positions[0].y,
				_size.width, _size.height));
		g2.fill(new Rectangle2D.Double(_positions[2].x, _positions[1].y,
				_size.height, _size.width));
		g2.fill(new Rectangle2D.Double(_positions[1].x, _positions[2].y,
				_size.width, _size.height));
		g2.fill(new Rectangle2D.Double(_positions[0].x, _positions[1].y,
				_size.height, _size.width));

		// Now draw the lamps within the stop lights.
		// Again, start with the northern light.
		g2.setPaint(_lights[NORTH][GREEN]);
		g2.fill(new Ellipse2D.Double((_positions[1].x + LAMP_SPACE),
				(_positions[0].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[NORTH][YELLOW]);
		g2.fill(new Ellipse2D.Double((_positions[1].x + LAMP_SPACE),
				(_positions[0].y + (LAMP_SPACE * 2) + LAMP_DIAMETER),
				LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[NORTH][RED]);
		g2.fill(new Ellipse2D.Double((_positions[1].x + LAMP_SPACE),
				(_positions[0].y + (LAMP_SPACE * 3) + (LAMP_DIAMETER * 2)),
				LAMP_DIAMETER, LAMP_DIAMETER));

		// Eastern light.
		g2.setPaint(_lights[EAST][RED]);
		g2.fill(new Ellipse2D.Double((_positions[2].x + LAMP_SPACE),
				(_positions[1].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[EAST][YELLOW]);
		g2.fill(new Ellipse2D.Double(
				(_positions[2].x + (LAMP_SPACE * 2) + LAMP_DIAMETER),
				(_positions[1].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[EAST][GREEN]);
		g2.fill(new Ellipse2D.Double(
				(_positions[2].x + (LAMP_SPACE * 3) + (LAMP_DIAMETER * 2)),
				(_positions[1].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));

		// Southern light.
		g2.setPaint(_lights[SOUTH][RED]);
		g2.fill(new Ellipse2D.Double((_positions[1].x + LAMP_SPACE),
				(_positions[2].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[SOUTH][YELLOW]);
		g2.fill(new Ellipse2D.Double((_positions[1].x + LAMP_SPACE),
				(_positions[2].y + (LAMP_SPACE * 2) + LAMP_DIAMETER),
				LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[SOUTH][GREEN]);
		g2.fill(new Ellipse2D.Double((_positions[1].x + LAMP_SPACE),
				(_positions[2].y + (LAMP_SPACE * 3) + (LAMP_DIAMETER * 2)),
				LAMP_DIAMETER, LAMP_DIAMETER));

		// Western light.
		g2.setPaint(_lights[WEST][GREEN]);
		g2.fill(new Ellipse2D.Double((_positions[0].x + LAMP_SPACE),
				(_positions[1].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[WEST][YELLOW]);
		g2.fill(new Ellipse2D.Double(
				(_positions[0].x + (LAMP_SPACE * 2) + LAMP_DIAMETER),
				(_positions[1].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
		g2.setPaint(_lights[WEST][RED]);
		g2.fill(new Ellipse2D.Double(
				(_positions[0].x + (LAMP_SPACE * 3) + (LAMP_DIAMETER * 2)),
				(_positions[1].y + LAMP_SPACE), LAMP_DIAMETER, LAMP_DIAMETER));
	}

	public void handleTimeout() {
		// Get rid of the current timer.
		_stoplightTimer = null;

		_fsm.Timeout();
		return;
	}

	// ----------------------------------------
	// State Machine Actions
	//
	public void InitLights() {
		// The lights are initially all black.
		int i;
		int j;
		for (i = 0; i < 4; ++i) {
			for (j = 0; j < 3; ++j) {
				_lights[i][j] = Color.black;
			}
		}

		return;
	}

	public void TurnLight(String light, String color) {
		if (light.compareTo("EWLIGHT") == 0) {
			if (color.compareTo("RED") == 0) {
				_lights[EAST][GREEN] = Color.black;
				_lights[EAST][YELLOW] = Color.black;
				_lights[EAST][RED] = Color.red;
				_lights[WEST][GREEN] = Color.black;
				_lights[WEST][YELLOW] = Color.black;
				_lights[WEST][RED] = Color.red;
			} else if (color.compareTo("GREEN") == 0) {
				_lights[EAST][GREEN] = Color.green;
				_lights[EAST][YELLOW] = Color.black;
				_lights[EAST][RED] = Color.black;
				_lights[WEST][GREEN] = Color.green;
				_lights[WEST][YELLOW] = Color.black;
				_lights[WEST][RED] = Color.black;
			} else if (color.compareTo("YELLOW") == 0) {
				_lights[EAST][GREEN] = Color.black;
				_lights[EAST][YELLOW] = Color.yellow;
				_lights[EAST][RED] = Color.black;
				_lights[WEST][GREEN] = Color.black;
				_lights[WEST][YELLOW] = Color.yellow;
				_lights[WEST][RED] = Color.black;
			}
		} else if (light.compareTo("NSLIGHT") == 0) {
			if (color.compareTo("RED") == 0) {
				_lights[NORTH][GREEN] = Color.black;
				_lights[NORTH][YELLOW] = Color.black;
				_lights[NORTH][RED] = Color.red;
				_lights[SOUTH][GREEN] = Color.black;
				_lights[SOUTH][YELLOW] = Color.black;
				_lights[SOUTH][RED] = Color.red;
			} else if (color.compareTo("GREEN") == 0) {
				_lights[NORTH][GREEN] = Color.green;
				_lights[NORTH][YELLOW] = Color.black;
				_lights[NORTH][RED] = Color.black;
				_lights[SOUTH][GREEN] = Color.green;
				_lights[SOUTH][YELLOW] = Color.black;
				_lights[SOUTH][RED] = Color.black;
			} else if (color.compareTo("YELLOW") == 0) {
				_lights[NORTH][GREEN] = Color.black;
				_lights[NORTH][YELLOW] = Color.yellow;
				_lights[NORTH][RED] = Color.black;
				_lights[SOUTH][GREEN] = Color.black;
				_lights[SOUTH][YELLOW] = Color.yellow;
				_lights[SOUTH][RED] = Color.black;
			}
		}
		return;
	}

	public void InformCanvas(String greenlight) {
		// Tell the owning canvas that the light has changed.
		// Also tell the canvas if a light is now green.
		if (greenlight.compareTo("YELLOW") == 0) {
			_owner.lightChanged(TrafficCanvas.YELLOW_LIGHT);
		} else if (greenlight.compareTo("EastWest") == 0) {
			_owner.lightChanged(TrafficCanvas.EW_LIGHT);
		} else {
			_owner.lightChanged(TrafficCanvas.NS_LIGHT);
		}

		return;
	}

	public void SetTimer(String direction) {
		int duration;

		if (direction.compareTo("NorthSouth") == 0) {
			duration = _nsLightDuration;
		} else if (direction.compareTo("EastWest") == 0) {
			duration = _ewLightDuration;
		} else {
			duration = _yellowDuration;
		}

		SetTimer(duration);

		return;
	}

	public void SetTimer(int duration) {
		// Remember when this timer was set and how long it
		// lasts. This information is needed for pausing and
		// continuing.
		_timerStart = new java.util.Date();
		_timerDuration = duration;

		// If the timer already exists, then get rid of it.
		if (_stoplightTimer != null) {
			// If the timer is running, then stop it, reset
			// its timeout value and run it again.
			if (_stoplightTimer.isRunning() == true) {
				_stoplightTimer.stop();
			}

			_stoplightTimer = null;
		}

		_stoplightTimer = new Timer(_timerDuration,
				new StoplightTimeoutListener(this));
		_stoplightTimer.setRepeats(false);
		_stoplightTimer.start();

		return;
	}

	public void PauseTimer() {
		// Figure out how much time is left on the timer and
		// use that value when continuing the timer.
		java.util.Date currTime = new java.util.Date();

		// Stop the timer now and do the calculations after.
		if (_stoplightTimer != null && _stoplightTimer.isRunning() == true) {
			_stoplightTimer.stop();
		}

		_timerDuration = _timerDuration
				- (int) (currTime.getTime() - _timerStart.getTime());
		if (_timerDuration < 0) {
			_timerDuration = 0;
		}
		_timerStart = null;

		return;
	}

	public void ContinueTimer() {
		// Pick up where we left off by restarting the timer.
		SetTimer(_timerDuration);

		return;
	}

	public void StopTimer() {
		// Stop the timer and clear the timer data.
		if (_stoplightTimer != null && _stoplightTimer.isRunning() == true) {
			_stoplightTimer.stop();
		}
		_stoplightTimer = null;
		_timerDuration = 0;
		_timerStart = null;

		return;
	}

	private Point[] _positions;
	private Dimension _size;
	private TrafficCanvas _owner;
	private StoplightContext _fsm;

	// Keep track of each light's current color.
	private Color[][] _lights;

	// Remember when the timer was started and how long the
	// timer is running.
	private java.util.Date _timerStart;
	private int _timerDuration;
	private Timer _stoplightTimer;

	// Stop light timing.
	private int _nsLightDuration;
	private int _ewLightDuration;
	private int _yellowDuration;

	// ----------------------------------------
	// Static data.
	//
	public static final int NORTH = 0;
	public static final int EAST = 1;
	public static final int SOUTH = 2;
	public static final int WEST = 3;

	private static final int LAMP_DIAMETER = 6;
	private static final int LAMP_SPACE = 3;
	private static final int GREEN = 0;
	private static final int YELLOW = 1;
	private static final int RED = 2;

	// ----------------------------------------
	// Nested classes.
	//
	private final class StoplightTimeoutListener implements ActionListener {
		public StoplightTimeoutListener(Stoplight owner) {
			_owner = owner;
		}

		public void actionPerformed(ActionEvent e) {
			_owner.handleTimeout();
			return;
		}

		private Stoplight _owner;
	}
}
